package easier.socket.encode;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

public class MyClass {

    public static void main( String[] args ) {

        KeyHolder keyHolder = genKeyPair();

        byte[] temp = new byte[20];
        boolean flagMotEnd = true;

        try {
            ServerSocket socket = new ServerSocket( 9091 );
            startThread();
            System.out.println( "accept" );
            Socket accept = socket.accept();
            InputStream inputStream = accept.getInputStream();
            OutputStream outputStream = accept.getOutputStream();
            BufferedWriter writer = new BufferedWriter( new OutputStreamWriter( outputStream ) );
            BufferedReader reader = new BufferedReader( new InputStreamReader( inputStream ) );

            /*1.发送公钥*/
            System.out.println( "发送公钥：" + keyHolder.getPubKey() );
            writer.write( keyHolder.getPubKey() );
            writer.newLine();
            writer.flush();

            /*4.接受Aes key*/
            String s = reader.readLine();
            String aesEncodeKey = rsaDecode( s, keyHolder.getSelfKey() );
            System.out.println( "收到AES key:" + aesEncodeKey );

            /*6.接收AES加密数据*/
            String s1 = reader.readLine();
            String encodeData = AESDecode( s1, aesEncodeKey );
            System.out.println( "收到加密数据:" + encodeData );

            /*8.获取签名文件*/
            String s2 = reader.readLine();
            System.out.println( "收到签名文件:" + s2 );

            /*10.验证签名文件*/
            String s3 = reader.readLine();
            boolean verify = verify( encodeData, s3, s2 );
            System.out.println( "收到签名公钥,验证签名:" + verify );

        } catch ( IOException e ) {
            e.printStackTrace();
        }
    }

    private static void startThread() {

        new Thread( new Runnable() {
            @Override
            public void run() {

                try {
                    Socket socket = new Socket( "127.0.0.1", 9091 );
                    OutputStream outputStream = socket.getOutputStream();
                    InputStream inputStream = socket.getInputStream();
                    BufferedReader reader = new BufferedReader( new InputStreamReader( inputStream ) );
                    BufferedWriter writer = new BufferedWriter( new OutputStreamWriter( outputStream ) );

                    /*2.接受公钥*/
                    String pub = reader.readLine();
                    System.out.println( "接受公钥：" + pub );

                    /*3.发送AES密码*/
                    SecretKey secretKey = AESKey();
                    String encodeAesKey = Base64.getEncoder().encodeToString( secretKey.getEncoded() );
                    System.out.println( "创建AES key:" + encodeAesKey );
                    String rsaEncode = rsaEncode( encodeAesKey, pub );
                    System.out.println( "发送AES key:" + rsaEncode );
                    writer.write( rsaEncode );
                    writer.newLine();
                    writer.flush();

                    /*5.发送AES加密的数据*/
                    String data = "曾经有一份真诚的爱情摆在我的面前，但是我没有珍惜。等到了失去的时候才后悔莫及，尘世间最痛苦的事莫过于此。如果上天可以给我一个机会再来一次的话，我会对你说三个字‘我爱你’。如果非要把这份爱加上一个期限，我希望是一万年！";
                    String encodeData = AESEncode( data, encodeAesKey );
                    System.out.println( "发送加密数据:" + data );
                    writer.write( encodeData );
                    writer.newLine();
                    writer.flush();

                    /*7.RSA签名密钥*/
                    KeyHolder keyHolderSign = genKeyPair();
                    String sign = sign( data, keyHolderSign.getSelfKey() );
                    System.out.println( "发送签名文件:" + sign );
                    writer.write( sign );
                    writer.newLine();
                    writer.flush();

                    /*9.发送签名公钥*/
                    System.out.println( "发送签名公钥:" + keyHolderSign.getPubKey() );
                    writer.write( keyHolderSign.getPubKey() );
                    writer.newLine();
                    writer.flush();

                } catch ( IOException e ) {
                    e.printStackTrace();
                }
            }
        } ).start();
    }


    /**
     * @return 生成RSA密钥对
     */
    public static KeyHolder genKeyPair() {

        try {
            KeyPairGenerator generator = KeyPairGenerator.getInstance( "RSA" );
            generator.initialize( 1024, new SecureRandom() );
            KeyPair keyPair = generator.generateKeyPair();
            PrivateKey aPrivate = keyPair.getPrivate();
            PublicKey aPublic = keyPair.getPublic();

            String x = new String( Base64.getEncoder().encode( aPrivate.getEncoded() ) );
            String s = new String( Base64.getEncoder().encode( aPublic.getEncoded() ) );

            return new KeyHolder( x, s );

        } catch ( NoSuchAlgorithmException e ) {
            e.printStackTrace();
        }

        return new KeyHolder( "", "" );
    }

    /**
     * 保存密钥对数据
     */
    public static class KeyHolder {

        private String selfKey;
        private String pubKey;

        public KeyHolder( String selfKey, String pubKey ) {

            this.selfKey = selfKey;
            this.pubKey = pubKey;
        }

        public String getSelfKey() {

            return selfKey;
        }

        public void setSelfKey( String selfKey ) {

            this.selfKey = selfKey;
        }

        public String getPubKey() {

            return pubKey;
        }

        public void setPubKey( String pubKey ) {

            this.pubKey = pubKey;
        }
    }

    /**
     * @param data
     * @param pubKey
     *
     * @return
     */
    public static String rsaEncode( String data, String pubKey ) {

        byte[] decode = Base64.getDecoder().decode( pubKey );
        try {
            PublicKey publicKey = KeyFactory.getInstance( "RSA" ).generatePublic( new X509EncodedKeySpec( decode ) );
            Cipher rsa = Cipher.getInstance( "RSA" );
            rsa.init( Cipher.ENCRYPT_MODE, publicKey );
            byte[] resultEncrypt = getMaxResultEncrypt( data, rsa );
            String s = Base64.getEncoder().encodeToString( resultEncrypt );

            System.out.println( "rsa公钥加密后数据:" + s );
            return s;

        } catch ( InvalidKeySpecException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e ) {
            e.printStackTrace();
        }

        return "";
    }

    private static String rsaDecode( String data, String priKey ) {

        byte[] decodeKey = Base64.getDecoder().decode( priKey );
        try {
            PrivateKey privateKey = KeyFactory.getInstance( "RSA" ).generatePrivate( new PKCS8EncodedKeySpec( decodeKey ) );
            Cipher rsa = Cipher.getInstance( "RSA" );
            rsa.init( Cipher.DECRYPT_MODE, privateKey );
            byte[] bytes = getMaxResultDecrypt( data, rsa );
            return new String( bytes );

        } catch ( Exception e ) {
            e.printStackTrace();
        }

        return "";
    }

    private static byte[] getMaxResultEncrypt( String str, Cipher cipher ) throws IllegalBlockSizeException, BadPaddingException {

        byte[] inputArray = str.getBytes();
        int inputLength = inputArray.length;
        // 最大加密字节数，超出最大字节数需要分组加密
        int MAX_ENCRYPT_BLOCK = 117;
        // 标识
        int offSet = 0;
        byte[] resultBytes = {};
        byte[] cache = {};
        while ( inputLength - offSet > 0 ) {
            if ( inputLength - offSet > MAX_ENCRYPT_BLOCK ) {
                cache = cipher.doFinal( inputArray, offSet, MAX_ENCRYPT_BLOCK );
                offSet += MAX_ENCRYPT_BLOCK;
            } else {
                cache = cipher.doFinal( inputArray, offSet, inputLength - offSet );
                offSet = inputLength;
            }
            resultBytes = Arrays.copyOf( resultBytes, resultBytes.length + cache.length );
            System.arraycopy( cache, 0, resultBytes, resultBytes.length - cache.length, cache.length );
        }
        return resultBytes;
    }

    private static byte[] getMaxResultDecrypt( String str, Cipher cipher )
        throws IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {

        byte[] inputArray = Base64.getDecoder().decode( str );
        int inputLength = inputArray.length;
        // 最大解密字节数，超出最大字节数需要分组加密
        int MAX_ENCRYPT_BLOCK = 128;
        // 标识
        int offSet = 0;
        byte[] resultBytes = {};
        byte[] cache = {};
        while ( inputLength - offSet > 0 ) {
            if ( inputLength - offSet > MAX_ENCRYPT_BLOCK ) {
                cache = cipher.doFinal( inputArray, offSet, MAX_ENCRYPT_BLOCK );
                offSet += MAX_ENCRYPT_BLOCK;
            } else {
                cache = cipher.doFinal( inputArray, offSet, inputLength - offSet );
                offSet = inputLength;
            }
            resultBytes = Arrays.copyOf( resultBytes, resultBytes.length + cache.length );
            System.arraycopy( cache, 0, resultBytes, resultBytes.length - cache.length, cache.length );
        }
        return resultBytes;
    }

    private static SecretKey AESKey() {

        //获取一个密钥生成器实例
        KeyGenerator keyGenerator = null;
        try {
            keyGenerator = KeyGenerator.getInstance( "AES" );
            SecureRandom random = new SecureRandom();
            keyGenerator.init( random );
            return keyGenerator.generateKey();
        } catch ( NoSuchAlgorithmException e ) {
            e.printStackTrace();
        }

        return null;
    }

    private static String AESEncode( String data, String key ) {

        byte[] decode = Base64.getDecoder().decode( key );

        try {
            Key keySpec = new SecretKeySpec( decode, "AES" );
            Cipher aes = Cipher.getInstance( "AES" );
            aes.init( Cipher.ENCRYPT_MODE, keySpec );
            byte[] bytes = aes.doFinal( data.getBytes() );
            return Base64.getEncoder().encodeToString( bytes );
        } catch ( Exception e ) {
            e.printStackTrace();
        }

        return "";
    }

    private static String AESDecode( String data, String key ) {

        byte[] decode = Base64.getDecoder().decode( key );
        byte[] dataDecode64 = Base64.getDecoder().decode( data );

        try {
            Key keySpec = new SecretKeySpec( decode, "AES" );
            Cipher aes = Cipher.getInstance( "AES" );
            aes.init( Cipher.DECRYPT_MODE, keySpec );
            byte[] bytes = aes.doFinal( dataDecode64 );
            return new String( bytes );
        } catch ( Exception e ) {
            e.printStackTrace();
        }

        return "";
    }

    private static String sign( String data, String priKey ) {

        byte[] decode = Base64.getDecoder().decode( priKey );
        try {
            PrivateKey privateKey = KeyFactory.getInstance( "RSA" ).generatePrivate( new PKCS8EncodedKeySpec( decode ) );
            Signature signature = Signature.getInstance( "MD5withRSA" );
            signature.initSign( privateKey );
            signature.update( data.getBytes() );
            return new String( Base64.getEncoder().encodeToString( signature.sign() ) );
        } catch ( Exception e ) {
            e.printStackTrace();
        }

        return "";
    }

    private static boolean verify( String srcData, String pubKey, String sign ) {

        byte[] decodePubKey = Base64.getDecoder().decode( pubKey );
        try {
            PublicKey rsa = KeyFactory.getInstance( "RSA" ).generatePublic( new X509EncodedKeySpec( decodePubKey ) );
            Signature signature = Signature.getInstance( "MD5withRSA" );
            signature.initVerify( rsa );
            signature.update( srcData.getBytes() );
            return signature.verify( Base64.getDecoder().decode( sign.getBytes() ) );
        } catch ( Exception e ) {
            e.printStackTrace();
        }

        return false;
    }
}